自定义View(3) -- 字母索引

效果图:
字母索引

自定义view的流程,具体请点此查看:自定义view套路
我们先重写构造器,然后重写onMeasure函数进行测量设置宽高,在本例中,宽我是根据padding和测量一个字母w的宽度来设置的,高度就是默认的,因为一般是设置为match_parent.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public AlphabeticalIndexView(Context context) {
this(context, null);
}

public AlphabeticalIndexView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}

public AlphabeticalIndexView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int w = (int) (getPaddingLeft() + getPaddingRight() + DisplayUtil.getTextWidth("W", mPaint));
int h = getMeasuredHeight();
setMeasuredDimension(w, h);
}


private void init(Context context) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextSize(DisplayUtil.sp2px(context, 15));
}

因为不是viewGroup,所以不必重写onLayout函数,我们直接进入onDraw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 每个字母所占用的高度
for (int i = 0; i < mLetters.length; i++) {
String letter = mLetters[i];
if (letter.equals(mCurrentTouchLetter))
mPaint.setColor(Color.BLUE);
else
mPaint.setColor(Color.GRAY);

// 获取字体的宽度
float measureTextWidth = mPaint.measureText(letter);
// 获取内容的宽度
int contentWidth = getWidth() - getPaddingLeft() - getPaddingRight();

canvas.drawText(letter, getPaddingLeft() + (contentWidth - measureTextWidth) / 2, getPaddingTop() + mSingLetterHeight * i + DisplayUtil.getTextBaseLine(mPaint), mPaint);
}
}

这里可能有点疑惑,为什么要用内容的宽度减去字体的宽度然后除于2,这是因为每个字母的宽度都不一样,如果不这样做的画,那么比如I就会和A W等字母左对齐,我们这样做的目的就是为了都居中。
这样我们就实现了一个静态的页面没我们要让他触摸改变,我们就重写onTouchEvent进行操作,并且添加相应的回调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Override
public boolean onTouchEvent(MotionEvent ev) {

switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
// 获取当前手指触摸的Y位置
float fingerY = ev.getY();
int pos = (int) (fingerY / mSingLetterHeight);
if (pos > -1 && pos < mLetters.length && !mLetters[pos].equals(mCurrentTouchLetter)) {
mCurrentTouchLetter = mLetters[pos];
triggerTouchListener(true);
invalidate();
}
break;
case MotionEvent.ACTION_UP:
triggerTouchListener(false);
break;
}

return true;
}

private void triggerTouchListener(boolean isTouch) {
if (mTouchListener != null)
mTouchListener.onTouch(mCurrentTouchLetter, isTouch);
}

// 设置触摸监听
private SideBarTouchListener mTouchListener;

public void setOnSideBarTouchListener(SideBarTouchListener touchListener) {
this.mTouchListener = touchListener;
}

public interface SideBarTouchListener {
void onTouch(String letter, boolean isTouch);
}

这样我们就可以实现了这个功能点。
优化思路
一个view绘制出来我们还需要进行优化,这一步是非常重要的。在本例中,我们的优化主要是在 onTouchEvent里面,因为我们知道,invalidate函数是一个一个做了很多事情的函数,具体请看:Android invalidate流程分析 我们要减少它的调用,所以我们应该先判断一下,如果当前的这个索引和触摸的这个不一样的话才调用invalidate,这样的话就优雅很多了。在本例中我没有将需要改变的属性,比如字体大小、选中颜色、默认颜色等提取在attr里面,需要的请自行添加。
参考:Android字母索引列表

源码github下载地址:
https://github.com/ChinaZeng/CustomView

-------------The End-------------